package org.gwt.grin.client.input;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.gwt.grin.client.Show;
import org.gwt.grin.client.commands.Command;

import com.google.gwt.event.dom.client.KeyCodes;

/**
 * This class is used to manage constants related to the remote control keys.
 * Instances of RCKeyEvent can be queued as GRIN commands; when they are
 * executed, the show will process the keystroke. The built-in
 * {@code RCKeyEvent} class is used for key pressed and key released events,
 * taken from a strictly limited set of keys.
 * <p>
 * If desired, a GRIN extension can subclass RCKeyEvent to add support for key
 * typed events. GRIN doesn't really support key typed events, but it does
 * provide support for queuing them and dispatching them in the animation
 * thread/loop (as is done with key pressed and released events).
 * 
 * @author Bill Foote (http://jovial.com)
 * @author ggeorg
 */
public class RCKeyEvent extends Command {

	/*
	 * Constants for key pressed events. Key released events are built lazily
	 * from the corresponding key pressed events. Key typed events are not
	 * handled here; they are responsibility of a GRIN extension, done as
	 * subclass of RCKeyEvent.
	 */
	public static final RCKeyEvent KEY_0;
	public static final RCKeyEvent KEY_1;
	public static final RCKeyEvent KEY_2;
	public static final RCKeyEvent KEY_3;
	public static final RCKeyEvent KEY_4;
	public static final RCKeyEvent KEY_5;
	public static final RCKeyEvent KEY_6;
	public static final RCKeyEvent KEY_7;
	public static final RCKeyEvent KEY_8;
	public static final RCKeyEvent KEY_9;
	public static final RCKeyEvent KEY_RIGHT;
	public static final RCKeyEvent KEY_LEFT;
	public static final RCKeyEvent KEY_UP;
	public static final RCKeyEvent KEY_DOWN;
	public static final RCKeyEvent KEY_ENTER;
	public static final RCKeyEvent KEY_SPACE;
	public static final RCKeyEvent KEY_ESC;

	private static Map<String, RCKeyEvent> keyByName = new HashMap<String, RCKeyEvent>();
	private static Map<Integer, RCKeyEvent> keyByEventCode = new HashMap<Integer, RCKeyEvent>();

	static {
		KEY_0 = new RCKeyEvent("0", '0', 0x00000001);
		KEY_1 = new RCKeyEvent("1", '1', 0x00000002);
		KEY_2 = new RCKeyEvent("2", '2', 0x00000004);
		KEY_3 = new RCKeyEvent("3", '3', 0x00000008);
		KEY_4 = new RCKeyEvent("4", '4', 0x00000010);
		KEY_5 = new RCKeyEvent("5", '5', 0x00000020);
		KEY_6 = new RCKeyEvent("6", '6', 0x00000040);
		KEY_7 = new RCKeyEvent("7", '7', 0x00000080);
		KEY_8 = new RCKeyEvent("8", '8', 0x00000100);
		KEY_9 = new RCKeyEvent("9", '9', 0x00000200);
		KEY_RIGHT = new RCKeyEvent("right", KeyCodes.KEY_RIGHT, 0x00000400);
		KEY_LEFT = new RCKeyEvent("left", KeyCodes.KEY_LEFT, 0x00000800);
		KEY_UP = new RCKeyEvent("up", KeyCodes.KEY_UP, 0x00001000);
		KEY_DOWN = new RCKeyEvent("down", KeyCodes.KEY_DOWN, 0x00002000);
		KEY_ENTER = new RCKeyEvent("enter", KeyCodes.KEY_ENTER, 0x00004000);
		KEY_SPACE = new RCKeyEvent("space", ' ', 0x00008000);
		KEY_ESC = new RCKeyEvent("esc", KeyCodes.KEY_ESCAPE, 0x00010000);
	}

	private String name; // human-readable name, used in script file
	private int keyCode;
	private int mask; // Mask value that we assign

	private RCKeyEvent keyReleased;

	// The key released event for this key. If this instance represents
	// a key pressed event, this will be null, or a value that's != this.
	// For a subclass defined to represent key typed events, this will
	// be null, abd won't be accessed.

	private RCKeyEvent(String name, int keyCode, int mask) {
		super(null); // the show data member of Command will be null
		this.name = name;
		this.keyCode = keyCode;
		this.mask = mask;
		keyByName.put(name, this);
		keyByEventCode.put(keyCode, this);
	}

	/**
	 * Initialize a new key typed event. This constructor is not used be the
	 * internal GRIN support for key pressed and key released handlers; it is
	 * intended for a subclass of {@code RCKeyEvent} that extends GRIN to
	 * support key typed events. It is up to the extension whether or not it
	 * choose to pre-allocate a number of constants, as is done in GRIN, or to
	 * allocate a new with each arriving event.
	 * <p>
	 * NOTE: If you override {@code RCKeyEvent}, be sure to override
	 * {@link #execute(Show)}.
	 * 
	 * @param keyCode
	 *            the {@link KeyCodes} code of the key
	 * @param mask
	 *            the bit mask of this key event
	 * 
	 * @see #execute(Show)
	 * @see Segment#keyTypesInterest
	 */
	protected RCKeyEvent(int keyCode, int mask) {
		super(null); // The show data member of Command will be null
		this.keyCode = keyCode;
		this.mask = mask;
	}

	// constructor for key released instances, see getKeyReleased().
	private RCKeyEvent() {
		super(null);
	}

	/**
	 * Get a developer-friendly name of this key event. Useful for debugging.
	 * 
	 * @return the name of this key event
	 */
	public String getName() {
		return name;
	}

	/**
	 * A bit mask value is assigned to each RC key (32 bit). Using a bitmask
	 * lets us check a key against a set of expected keys very efficiently.
	 * 
	 * @return the mask for this key
	 */
	public int getBitMask() {
		return mask;
	}

	/**
	 * 
	 * @return {@code true} if this represents key press, and {@code false} if
	 *         it represents a key release
	 */
	public boolean isKeyPress() {
		return keyReleased != this; // see keyReleased comment above
	}

	/**
	 * 
	 * @return the {@code RCKeyEvent} for this VK code that represents a key
	 *         release
	 */
	public RCKeyEvent getKeyReleased() {
		if (this.keyReleased == null) {
			this.keyReleased = new RCKeyEvent(); // doesn't add to table
			keyReleased.name = this.name;
			keyReleased.keyCode = this.keyCode;
			keyReleased.mask = this.mask;
			keyReleased.keyReleased = this.keyReleased;
		}
		return keyReleased;
	}

	@Override
	public void execute(Show caller) {
		if (isKeyPress()) {
			caller.internalHandleKeyPressed(this, caller);
		} else {
			caller.internalHandleKeyReleased(this, caller);
		}
	}

	@Override
	public void execute() {
		throw new IllegalStateException();
	}

	/**
	 * Look up the {@code RCKeyEvent} by its VK code.
	 * 
	 * @param key
	 * @return the {@code RCKeyEvent}, or {@code null} if there's no
	 *         corresponding event
	 */
	public static RCKeyEvent getKeyByEventCode(int key) {
		return keyByEventCode.get(key);
	}

	/**
	 * Look up the {@code RCKeyEvent} by its logical name.
	 * 
	 * @param name
	 * @return the {@code RCKeyEvent}, or {@code null} if there's no
	 *         corresponding event
	 */
	public static RCKeyEvent getByName(String name) {
		return keyByName.get(name);
	}

	/**
	 * 
	 * @param mask
	 * @return a list of events turned-on by the given mask
	 */
	public static List<RCKeyEvent> getEventsFromMask(int mask) {
		Collection<RCKeyEvent> values = keyByName.values();
		List<RCKeyEvent> result = new ArrayList<RCKeyEvent>();
		for (RCKeyEvent keyEvent : values) {
			if ((mask & keyEvent.getBitMask()) != 0) {
				result.add(keyEvent);
			}
		}
		return result;
	}
}
